﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace CodeTalk
{
    public class Context
    {
        private static uint _nextAvailableID = 0;
        protected static uint nextAvailableID
        {
            get { return ++_nextAvailableID; }
        }

        public string Name { get; private set; }
        public string FilePath { get; private set; }

        private List<CodeSymbol> symbols;
        public List<CodeSymbol> Symbols
        {
            get { return symbols; }
        }

        public bool IsLibrary { get; private set; }

        public Context(string FilePath)
        {
            this.FilePath = FilePath;
            this.Name = System.IO.Path.GetFileNameWithoutExtension(FilePath);
            symbols = new List<CodeSymbol>();
            IsLibrary = false;
        }

        internal CodeSymbol GetSymbolWithID(uint id)
        {
            for (int i = 0; i < symbols.Count; i++)
            {
                if (symbols[i].SymbolID == id)
                    return symbols[i];
            }
            return null;
        }

        internal CodeSymbol GetSymbolWithSpokenForm(string spokenForm)
        {
            for (int i = 0; i < symbols.Count; i++)
            {
                if (symbols[i].SpokenForm == spokenForm)
                    return symbols[i];
            }
            return null;
        }

        internal CodeSymbol GetSymbolWithSpokenForm(string spokenForm, SymbolCategory category)
        {
            for (int i = 0; i < symbols.Count; i++)
            {
                if (symbols[i].SpokenForm == spokenForm && symbols[i].Category == category)
                    return symbols[i];
            }
            return null;
        }

        private static bool ValidateNewSymbolAgainstCollection(string spokenForm, SymbolCategory category, SpeechMode currentMode, IEnumerable<CodeSymbol> symbols)
        {
            foreach (var s in symbols)
            {
                if (category == s.Category && s.SpokenForm == spokenForm.ToLowerInvariant())
                {
                    if ((s.Solution == "" || s.Solution == currentMode.CurrentSolution) && (s.File == "" || s.File == currentMode.CurrentWorkFile))
                        return false;
                }
            }

            return true;
        }

        public bool ValidateNewSymbol(string spokenForm, SymbolCategory category, SpeechMode currentMode, ref string errorMessage)
        {
            if (!ValidateNewSymbolAgainstCollection(spokenForm, category, currentMode, symbols))
            {
                errorMessage = "A " + category.ToString().ToLowerInvariant() + " with the same spoken form is already in the given scope and context.";
                return false;
            }

            foreach (var lib in currentMode.LibrariesUsed)
            {
                if (!ValidateNewSymbolAgainstCollection(spokenForm, category, currentMode, lib.Symbols))
                {
                    errorMessage = "A " + category.ToString().ToLowerInvariant() + " with the same spoken form is already in the given scope in library '" + lib.Name + "'.";
                    return false;
                }
            }

            return true;
        }

        public bool ValidateNewSymbolOld(string spokenForm, SymbolCategory category, SpeechMode currentMode, ref string errorMessage)
        {
            foreach (var s in symbols)
            {
                if (s.SpokenForm == spokenForm.ToLowerInvariant())
                {
                    if ((s.Category == SymbolCategory.Open || s.Category == SymbolCategory.Function || s.Category == SymbolCategory.Type) &&
                        (s.Solution == "" || s.Solution == currentMode.CurrentSolution))
                    {
                        errorMessage = "A symbol with the same spoken form is already in the given scope.";
                        return false;
                    }
                    else if (s.Category == SymbolCategory.Namespace)
                    {
                        errorMessage = "A namespace with the same spoken form already exists";
                        return false;
                    }
                    else if (s.Category == SymbolCategory.Local && category == SymbolCategory.Local &&
                        (s.Solution == "" || s.Solution == currentMode.CurrentSolution) && (s.File == "" || s.File == currentMode.CurrentWorkFile))
                    {
                        errorMessage = "A local identifier with the same spoken form is already in the given scope";
                        return false;
                    }
                    else if (s.Category == SymbolCategory.Hidden && category == SymbolCategory.Hidden &&
                        (s.Solution == "" || s.Solution == currentMode.CurrentSolution) && (s.File == "" || s.File == currentMode.CurrentWorkFile))
                    {
                        errorMessage = "A hidden identifier with the same spoken form is already in the given scope";
                        return false;
                    }
                }
            }

            return true;
        }

        /// <summary>
        /// Create a new symbol. 
        /// </summary>
        /// <param name="spokenForm">The spoken form of the symbol to be created. </param>
        /// <returns>The id of the new symbol. 0 if the symbol could not be created. </returns>
        internal uint CreateNewSymbol(string spokenForm, SymbolCategory category, SpeechMode currentMode, ref string errorMessage)
        {
            return CreateNewSymbol(spokenForm, spokenForm, category, currentMode, ref errorMessage);
        }

        /// <summary>
        /// Create a new symbol. 
        /// </summary>
        /// <param name="writtenForm">The written form of the symbol to be created. </param>
        /// <param name="spokenForm">The spoken form of the symbol to be created. </param>
        /// <param name="category">The category of the symbol to be created. </param>
        /// <returns>The id of the new symbol. 0 if the symbol could not be created. </returns>
        internal uint CreateNewSymbol(string writtenForm, string spokenForm, SymbolCategory category, SpeechMode currentMode, ref string errorMessage)
        {
            if (IsLibrary && (category == SymbolCategory.Hidden || category == SymbolCategory.Local))
            {
                errorMessage = "Cannot create symbol of category " + category.ToString() + " in a library context.";
                return 0;
            }

            // Test if a symbol of the same spoken form exists in the same scope. 
            if (!ValidateNewSymbol(spokenForm, category, currentMode, ref errorMessage))
                return 0;

            CodeSymbol cs = new CodeSymbol(writtenForm, spokenForm, category, nextAvailableID);
            symbols.Add(cs);
            return cs.SymbolID;
        }

        internal uint CreateNewSymbol(string writtenForm, string spokenForm, SymbolCategory category, SpeechMode currentMode, 
            bool permanent, string parentType, string file, string solution, ref string errorMessage)
        {
            if (IsLibrary && (category == SymbolCategory.Hidden || category == SymbolCategory.Local))
            {
                errorMessage = "Cannot create symbol of category " + category.ToString() + " in a library context.";
                return 0;
            }

            // Test if a symbol of the same spoken form exists in the same scope. 
            if (!ValidateNewSymbol(spokenForm, category, currentMode, ref errorMessage))
                return 0;

            CodeSymbol cs = new CodeSymbol(writtenForm, spokenForm, category, nextAvailableID);
            cs.Permanent = permanent;
            cs.ParentType = parentType;
            cs.File = file;
            cs.Solution = solution;
            symbols.Add(cs);
            return cs.SymbolID;
        }

        public bool Save()
        {
            return SaveToFile(FilePath);
        }

        public bool SaveAsLibrary(string filePath)
        {
            IsLibrary = true;

            // Delete all hidden or local symbols. 
            for (int i = 0; i < Symbols.Count; i++)
            {
                if (Symbols[i].Category == SymbolCategory.Hidden || Symbols[i].Category == SymbolCategory.Local)
                    Symbols.RemoveAt(i--);
            }

            foreach (var s in Symbols)
            {
                s.File = "";
                s.Solution = "";
            }

            return SaveToFile(filePath);
        }

        public bool SaveToFile(string filePath)
        {
            try
            {
                Name = Path.GetFileNameWithoutExtension(filePath);
                using (StreamWriter sw = new StreamWriter(filePath, false))
                {
                    sw.WriteLine("Context: " + Name);
                    sw.WriteLine("IsLibrary: " + IsLibrary);
                    int nSymbolsToSave = 0;
                    foreach (var s in symbols)
                        if (s.Permanent)
                            nSymbolsToSave++;

                    sw.WriteLine("Symbols: " + nSymbolsToSave);
                    foreach (var s in symbols)
                    {
                        if (s.Permanent)
                            sw.WriteLine(s.Serialize());
                    }
                }
                FilePath = filePath;
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        public static Context LoadFromFile(string filePath)
        {
            try
            {
                Context newContext = null;
                using (StreamReader sr = new StreamReader(filePath))
                {
                    string line = sr.ReadLine();
                    if (!line.StartsWith("Context: "))
                        return null;

                    newContext = new Context(filePath);

                    line = sr.ReadLine();
                    if (!line.StartsWith("IsLibrary: "))
                        return null;
                    line = line.Substring(11);
                    newContext.IsLibrary = bool.Parse(line);

                    line = sr.ReadLine();
                    if (!line.StartsWith("Symbols: "))
                        return null;
                    line = line.Substring(9);
                    int nSymbols = int.Parse(line);

                    for (int i = 0; i < nSymbols; i++)
                    {
                        line = sr.ReadLine();
                        CodeSymbol cs = CodeSymbol.Deserialize(line);
                        if (cs != null)
                        {
                            newContext.symbols.Add(cs);
                            if (cs.SymbolID > _nextAvailableID)
                                _nextAvailableID = cs.SymbolID;
                        }
                    }
                }
                return newContext;
            }
            catch (Exception)
            {
                return null;
            }
        }

        internal bool DeleteSymbolWithID(uint symbolID)
        {
            for (int i = 0; i < symbols.Count; i++)
            {
                if (symbols[i].SymbolID == symbolID)
                {
                    symbols.RemoveAt(i);
                    return true;
                }
            }
            return false;
        }
    }
}
